import { ActionSheetIOS, Linking } from "react-native";
import { EmailException } from "./email-exception";

const prefixes = {
  "apple-mail": "message://",
  gmail: "googlegmail://",
  inbox: "inbox-gmail://",
  spark: "readdle-spark://",
  airmail: "airmail://",
  outlook: "ms-outlook://",
  yahoo: "ymail://",
  superhuman: "superhuman://",
  yandex: "yandexmail://",
  fastmail: "fastmail://",
  protonmail: "protonmail://",
  seznamemail: "szn-email://",
};

const titles = {
  "apple-mail": "Mail",
  gmail: "Gmail",
  inbox: "Inbox",
  spark: "Spark",
  airmail: "Airmail",
  outlook: "Outlook",
  yahoo: "Yahoo Mail",
  superhuman: "Superhuman",
  yandex: "Yandex",
  fastmail: "Fastmail",
  protonmail: "ProtonMail",
  seznamemail: "Email.cz",
};

/**
 * Allowed params for each app url
 *  - apple-mail: https://ios.gadgethacks.com/news/always-updated-list-ios-app-url-scheme-names-0184033/
 *  - gmail: https://stackoverflow.com/questions/32114455/open-gmail-app-from-my-app
 *  - inbox: https://stackoverflow.com/questions/29655978/is-there-an-ios-mail-scheme-url-for-googles-inbox
 *  - spark: https://helpspot.readdle.com/spark/index.php?pg=kb.page&id=791
 *  - airmail: https://help.airmailapp.com/en-us/article/airmail-ios-url-scheme-1q060gy/
 *  - outlook: https://stackoverflow.com/questions/32369198/i-just-want-to-open-ms-outlook-app-and-see-mailto-screen-using-url-scheme-at-ios
 *  - fastmail: https://github.com/vtourraine/ThirdPartyMailer/blob/1.8.0/Sources/ThirdPartyMailer/ThirdPartyMailClient.swift#L80
 */
const uriParams = {
  "apple-mail": {
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  gmail: {
    path: "co",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  inbox: {
    path: "co",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  spark: {
    path: "compose",
    to: "recipient",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  airmail: {
    path: "compose",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "htmlBody",
  },
  outlook: {
    path: "compose",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  yahoo: {
    path: "mail/compose",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  superhuman: {
    path: "compose",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  yandex: {
    path: "compose",
  },
  fastmail: {
    path: "mail/compose",
    to: "to",
    cc: "cc",
    bcc: "bcc",
    subject: "subject",
    body: "body",
  },
  protonmail: {
    path: "compose",
  },
  seznamemail: {
    path: "mail",
  },
};

/**
 * Returns param to open app compose screen and pre-fill 'to', 'subject' and 'body',
 * @param {string} app
 * @param {{
 *     to: string,
 *     cc: string,
 *     bcc: string,
 *     subject: string,
 *     body: string,
 * }} options
 */
function getUrlParams(app, options) {
  const appParms = uriParams[app];
  if (!appParms) {
    return "";
  }

  const path = app === "apple-mail" ? options.to || "" : appParms.path;
  const urlParams = Object.keys(appParms).reduce((params, currentParam) => {
    if (options[currentParam]) {
      params.push(`${appParms[currentParam]}=${options[currentParam]}`);
    }
    return params;
  }, []);

  return `${path}?${urlParams.join("&")}`;
}

/**
 * Check if a given mail app is installed.
 *
 * @param {string} app
 * @returns {Promise<boolean>}
 */
export function isAppInstalled(app) {
  return new Promise((resolve) => {
    if (!(app in prefixes)) {
      return resolve(false);
    }

    Linking.canOpenURL(prefixes[app])
      .then((isSupported) => {
        resolve(isSupported);
      })
      .catch(() => resolve(false));
  });
}

/**
 * Ask the user to choose one of the available mail apps.
 * @param title
 * @param message
 * @param cancelLabel
 * @param removeText
 * @param defaultEmailLabel
 * @returns {Promise<String|null>}
 */
export function askAppChoice(
  title = "Open mail app",
  message = "Which app would you like to open?",
  cancelLabel = "Cancel",
  removeText = false,
  defaultEmailLabel = "Default email reader",
  actionType = "open"
) {
  return new Promise(async (resolve, reject) => {
    let availableApps = [];
    for (let app in prefixes) {
      let avail = await isAppInstalled(app);
      if (avail) {
        availableApps.push(app);
      }
    }

    if (!availableApps.length) {
      return reject(new EmailException("No email apps available"));
    }
    if (availableApps.length === 1) {
      return resolve(availableApps[0]);
    }

    let options = availableApps.map((app) =>
      actionType === "compose" && app === "apple-mail"
        ? defaultEmailLabel
        : titles[app]
    );
    options.push(cancelLabel);

    ActionSheetIOS.showActionSheetWithOptions(
      {
        options: options,
        cancelButtonIndex: options.length - 1,
        ...(removeText ? {} : { title, message }),
      },
      (buttonIndex) => {
        if (buttonIndex === options.length - 1) {
          return resolve(null);
        }
        return resolve(availableApps[buttonIndex]);
      }
    );
  });
}

/**
 * Returns the name of the app provided in the options object or the app selected by the user.
 * @param {{
 *     app: string | undefined | null,
 * }} options
 * @param {("open" | "compose")} actionType
 * @return string
 */
async function getApp(options, actionType) {
  if (options && typeof options !== "object") {
    throw new EmailException("First parameter must be an object of options.");
  }

  if (
    "app" in options &&
    options.app &&
    Object.keys(prefixes).indexOf(options.app) < 0
  ) {
    throw new EmailException(
      'Option `app` should be undefined, null, or one of the following: "' +
        Object.keys(prefixes).join('", "') +
        '".'
    );
  }

  let { app = null } = options;

  if (!app) {
    const {
      title,
      message,
      cancelLabel,
      removeText,
      defaultEmailLabel,
    } = options;
    app = await askAppChoice(
      title,
      message,
      cancelLabel,
      removeText,
      defaultEmailLabel,
      actionType
    );
  }

  return app;
}

/**
 * Get available email clients
 *
 * @returns {Promise<{
 *   androidPackageName: string;
 *   title: string;
 *   prefix: string;
 *   iOSAppName: string;
 *   id: string;
 * }[]>}
 */
export function getEmailClients() {
  return new Promise(async (resolve, reject) => {
    let availableApps = [];
    for (let app in prefixes) {
      let avail = await isAppInstalled(app);
      if (avail) {
        availableApps.push(app);
      }
    }

    if (availableApps.length === 0) {
      return reject(new EmailException("No email apps available"));
    }

    const apps = availableApps.reduce((acc, app) => {
      const title = titles[app] || "";

      if (title) {
        acc.push({
          androidPackageName: "", // Android only
          title,
          prefix: prefixes[app], // iOS only
          iOSAppName: app, // iOS only
          id: app,
        });

        return acc;
      }

      return acc;
    }, []);

    return resolve(apps);
  });
}

/**
 * Open an email app, or let the user choose what app to open.
 *
 * @param {{
 *     app: string | undefined | null,
 *     title: string,
 *     message: string,
 *     cancelLabel: string,
 *     removeText: boolean
 *     defaultEmailLabel: string
 * }} options
 */
export async function openInbox(options = {}) {
  const app = await getApp(options, "open");

  if (!app) {
    return null;
  }

  await Linking.openURL(prefixes[app]);
  return { app, title: titles[app] };
}

/**
 * Open an email app on the compose screen, or let the user choose what app to open on the compose screen.
 * You can pass `id` to open a specific app, or `null` to let the user choose. (`id` can be retrieved with `getEmailClients`
 *
 * @param {{
 *     app: string | undefined | null,
 *     title: string,
 *     message: string,
 *     cancelLabel: string,
 *     removeText: boolean,
 *     defaultEmailLabel: string,
 *     to: string,
 *     cc: string,
 *     bcc: string,
 *     subject: string,
 *     body: string,
 *     encodeBody: boolean
 * }} options
 */
export async function openComposer(options) {
  const app = await getApp(options, "compose");

  if (!app) {
    return null;
  }

  if (options.encodeBody) {
    options.body = encodeURIComponent(options.body);
  }

  const params = getUrlParams(app, options);
  let prefix = prefixes[app];

  if (app === "apple-mail") {
    // apple mail prefix to compose an email is mailto
    prefix = "mailto:";
  }

  await Linking.openURL(`${prefix}${params}`);
  return { app, title: titles[app] };
}
